<!--
@license
Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->

<link rel="import" href="templatizer.html">

<script>

  /**
   * Stamps the template iff the `if` property is truthy.
   *
   * When `if` becomes falsey, the stamped content is hidden but not
   * removed from dom. When `if` subsequently becomes truthy again, the content
   * is simply re-shown. This approach is used due to its favorable performance
   * characteristics: the expense of creating template content is paid only
   * once and lazily.
   *
   * Set the `restamp` property to true to force the stamped content to be
   * created / destroyed when the `if` condition changes.
   */
  Polymer({

    is: 'dom-if',
    extends: 'template',

    /**
     * Fired whenever DOM is added or removed/hidden by this template (by
     * default, rendering occurs lazily).  To force immediate rendering, call
     * `render`.
     *
     * @event dom-change
     */

    properties: {

      /**
       * A boolean indicating whether this template should stamp.
       */
      'if': {
        type: Boolean,
        value: false
      },

      /**
       * When true, elements will be removed from DOM and discarded when `if`
       * becomes false and re-created and added back to the DOM when `if`
       * becomes true.  By default, stamped elements will be hidden but left
       * in the DOM when `if` becomes false, which is generally results
       * in better performance.
       */
      restamp: {
        type: Boolean,
        value: false
      }

    },

    behaviors: [
      Polymer.Templatizer
    ],

    observers: [
      '_queueRender(if, restamp)'
    ],

    _queueRender: function() {
      this._debounceTemplate(this._render);
    },

    detached: function() {
      // TODO(kschaaf): add logic to re-stamp in attached?
      this._teardownInstance();
    },

    attached: function() {
      if (this.if && this.ctor) {
        // TODO(sorvell): should not be async, but node can be attached
        // when shady dom is in the act of distributing/composing so push it out
        this.async(this._ensureInstance);
      }
    },

    render: function() {
      this._flushTemplates();
    },

    _render: function() {
      if (this.if) {
        if (!this.ctor) {
          this._wrapTextNodes(this._content || this.content);
          this.templatize(this);
        }
        this._ensureInstance();
        this._showHideChildren();
      } else if (this.restamp) {
        this._teardownInstance();
      }
      if (!this.restamp && this._instance) {
        this._showHideChildren();
      }
      if (this.if != this._lastIf) {
        this.fire('dom-change');
        this._lastIf = this.if;
      }
    },

    _ensureInstance: function() {
      if (!this._instance) {
        // TODO(sorvell): pickup stamping logic from x-repeat
        this._instance = this.stamp();
        var root = this._instance.root;
        // TODO(sorvell): this incantation needs to be simpler.
        var parent = Polymer.dom(Polymer.dom(this).parentNode);
        parent.insertBefore(root, this);
      }
    },

    _teardownInstance: function() {
      if (this._instance) {
        var c = this._instance._children;
        if (c) {
          // use first child parent, for case when dom-if may have been detached
          var parent = Polymer.dom(Polymer.dom(c[0]).parentNode);
          c.forEach(function(n) {
            parent.removeChild(n);
          });
        }
        this._instance = null;
      }
    },

    _wrapTextNodes: function(root) {
      // wrap text nodes in span so they can be hidden.
      for (var n = root.firstChild; n; n=n.nextSibling) {
        if (n.nodeType === Node.TEXT_NODE) {
          var s = document.createElement('span');
          root.insertBefore(s, n);
          s.appendChild(n);
          n = s;
        }
      }
    },

    _showHideChildren: function() {
      var hidden = this._hideTemplateChildren || !this.if;
      if (this._instance) {
        var c$ = this._instance._children;
        for (var i=0; i<c$.length; i++) {
          var c = c$[i];
          c.style.display = hidden ? 'none' : '';
          c._hideTemplateChildren = hidden;
        }
      }
    },

    // Implements extension point from Templatizer mixin
    // Called as side-effect of a host property change, responsible for
    // notifying parent.<prop> path change on instance
    _forwardParentProp: function(prop, value) {
      if (this._instance) {
        this._instance[prop] = value;
      }
    },

    // Implements extension point from Templatizer
    // Called as side-effect of a host path change, responsible for
    // notifying parent.<path> path change on each row
    _forwardParentPath: function(path, value) {
      if (this._instance) {
        this._instance.notifyPath(path, value, true);
      }
    }

  });

</script>
